package robot;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Random;

import environnement.Environnement;
import java.awt.Paint;
import java.util.ArrayList;
import java.util.List;

import org.omg.CORBA.Environment;

import robot.io.capteurs.Absisse;
import robot.io.capteurs.AbsisseObjectif;
import robot.io.capteurs.ICapteur;
import robot.io.capteurs.Ordonne;
import robot.io.capteurs.OrdonneObjectif;
import robot.io.capteurs.PresenceDevant;
import robot.io.infos.Centre;
import robot.io.infos.IInfos;
import robot.io.sorties.Acceleration;
import robot.io.sorties.ISortie;
import robot.io.sorties.Rotation;

/**
 * Class RobotCourse
 * Robot ayant pour but la vitesse moyenne la plus rapide
 */
public class RobotCentre implements IRobot
{

    //
    // Fields
    //
    /**
     * absisse
     */
    private int x;
    /**
     * ordonné
     */
    private int y;
    /**
     * Matrice des poids
     */
    private double[][] matricePoids;
    /**
     * Liste des informations supplémentaires
     */
    private IInfos[] listeInfos;
    /**
     * Liste des capteurs
     */
    private ICapteur[] listeCapteurs;
    /**
     * Liste des sorties
     */
    private ISortie[] listeSorties;
    /**
     * Informations necessaires aux interactions entres actions
     */
    private HashMap<String, Integer> ioRobot;
    /**
     * Valeurs des neurones
     */
    private double[] valeursNeurones;
    /**
     * Score en 0 et 1
     */
    private double score;
    /**
     * Objet dessin du robot
     */
    private Rectangle2D.Double robot;
    /**
     * Objet Environnement (plateau de jeu)
     */
    private Environnement env;

    Paint ptColor = Color.BLACK;

    /**
     *
     */
    //
    // Constructors
    //
    /**
     *
     * @param x
     * @param y
     * @param listeInfos
     * @param listeCapteurs
     * @param listeSorties
     * @param score
     */
    public RobotCentre(int x, int y, int score)
    {
        // Initialisation des variables
        this.x = x;
        this.y = y;
        this.score = score;
        this.ioRobot = new HashMap<String, Integer>();
        this.env = Environnement.getInstance();

        Random r = new Random(System.nanoTime());

        //////////////////////////////////////////////////////
        //        Initialisation des entrees/sorties        //
        //              et info du robot                    //
        //////////////////////////////////////////////////////
        // Entrées
        this.listeCapteurs = new ICapteur[2];
        this.listeCapteurs[0] = new Absisse() ;
        //this.listeCapteurs[1] = new AbsisseObjectif() ;
        this.listeCapteurs[1] = new Ordonne() ;
        //this.listeCapteurs[3] = new OrdonneObjectif() ;
        //this.listeCapteurs[0] = new PresenceDevant();

        //Sorties
        this.listeSorties = new ISortie[2];
        this.listeSorties[0] = new Rotation();
        this.listeSorties[1] = new Acceleration();

        //Infos
        this.listeInfos = new IInfos[1];
        this.listeInfos[0] = new Centre();

        //////////////////////////////////////////////////////
        //         Referencement des ID des entrée / sorties//
        //////////////////////////////////////////////////////
        // Referencement des sorties
        for (int index = 0; index < this.listeSorties.length; ++index)
        {
            this.ioRobot.put(this.listeSorties[index].getClass().getName(), index);
        }

        // referencement des entrees
        for (int index = 0; index < this.listeCapteurs.length; ++index)
        {
            this.ioRobot.put(this.listeCapteurs[index].getClass().getName(), index);
        }

        // Referencement des infos
        for (int index = 0; index < this.listeInfos.length; ++index)
        {
            this.ioRobot.put(this.listeInfos[index].getClass().getName(), index);
        }

        //////////////////////////////////////////////////////
        //         Initialisation pour l'IA du robot        //
        //////////////////////////////////////////////////////
        int nb = 14;
        this.matricePoids = new double[nb][nb];

        for (int i = 0; i < nb; ++i)
        {
            for (int j = 0; j < nb; ++j)
            {
            	
            	
            	
            	if( i < this.listeCapteurs.length ) {//Neuronne representatn un capteur
            		if( j == i ) {
                        //this.matricePoids[i][j] = r.nextDouble();//L'entrée en entrée d'elle même
            			this.matricePoids[i][j] = 1 ;
            		}
            		else {
            			this.matricePoids[i][j] = 0 ;
            		}
            	}
            	
            	
            	
            	else if( i < this.matricePoids.length - this.listeSorties.length ) {//Neuronne de la couche cachée
            		if( j < this.listeCapteurs.length ||
            			j == i ||
            			j > this.matricePoids.length - this.listeSorties.length 
            				) {
            			this.matricePoids[i][j] = r.nextDouble();
            		}
            		else {
            			this.matricePoids[i][j] = 0 ;
            		}
            	}
            	
            	
            	
            	else {//Neuronne de sortie
            		if( ( j >= this.listeCapteurs.length &&
                			j < this.matricePoids.length - this.listeSorties.length )
            			||	j == i 
            			) {
            			this.matricePoids[i][j] = r.nextDouble();
            		}
            		else {
            			this.matricePoids[i][j] = 0 ;
            		}
            	}
            	
            	
            	
            }
        }

        this.valeursNeurones = new double[16];

        for (int i = 0; i < 16; ++i)
        {
            this.valeursNeurones[i] = r.nextDouble();
        }

    }

    //
    // Methods
    //
    //
    // Accessor methods
    //
    /**
     * Modifie la valeur de l'absisse
     */
    @Override
    public void setX(int x)
    {
        this.x = x;
    }

    /**
     * retourne la valeur de l'absisse
     */
    @Override
    public int getX()
    {
        return x;
    }

    /**
     * Modifie la valeur de l'ordonnée
     */
    @Override
    public void setY(int y)
    {
        this.y = y;

    }

    /**
     *
     */
    @Override
    public int getY()
    {
        return y;
    }

    /**
     * Retourne la variable correspondant à la clé
     */
    @Override
    public int getIndex(String key)
    {
        int var = 0;
        if (this.ioRobot.containsKey(key))
        {
            var = this.ioRobot.get(key);
        }
        else
        {
            var = 0;
        }
        return var;
    }

    @Override
    public void setIndex(String key, int valeur)
    {
        this.ioRobot.put(key, valeur);
    }

    @Override
    public void setMatricePoids(double[][] matrice)
    {
        this.matricePoids = matrice;
    }

    @Override
    public double[][] getMatricePoids()
    {
        return this.matricePoids;
    }

    @Override
    public void setListeInfos(IInfos[] infos)
    {
        this.listeInfos = infos;
    }

    @Override
    public IInfos[] getListeInfos()
    {
        return this.listeInfos;
    }

    @Override
    public void setListeCapteurs(ICapteur[] capteurs)
    {
        this.listeCapteurs = capteurs;
    }

    @Override
    public ICapteur[] getListeCapteurs()
    {
        return listeCapteurs;
    }

    @Override
    public void setListe_Sorties(ISortie[] sorties)
    {
        this.listeSorties = sorties;
    }

    @Override
    public ISortie[] getListe_Sorties()
    {
        return listeSorties;
    }

    @Override
    public void setScore(double score)
    {
        this.score = score;
    }

    @Override
    public double getScore()
    {
        return this.score;
    }

    //
    // Other methods
    //
    /**
     * Calcul le score suivant la vitesse moyenne du robot
     * @return       int
     * @param        robot Robot
     */
    public double calculScore()
    {
        
        /*
         * Calcul du score en distance
         */

        /*
         * Calcul du score en angle
         * 
         * Pas bon, à refaire, ça fait n'importe quoi
         * 
         */
        double angle = this.getListe_Sorties()[this.getIndex(Rotation.class.getName())].getValeur() ;
        
        if( angle < 0 ) { angle += 2 * Math.PI ; }
        
        double AxeX = (double)Environnement.getInstance().getLargeur()/2 - (double)this.getX() ;
        
        double AxeY = (double)Environnement.getInstance().getLongueur()/2 - (double)this.getY() ;
        
        double hypo = Math.sqrt( 
        		Math.pow(AxeX, 2) + 
        		Math.pow(AxeY, 2 )
        							) ;
        
        double angle_cible = Math.acos( AxeX / hypo ) ;
        
        double signe =  Math.asin( AxeY / hypo ) ;
        
        if( signe < 0 ) {
        	signe = -1 ;
        }
        else {
        	signe = 1;
        }
        
        angle_cible *= signe ;
        
        if( angle_cible < 0 ) { angle_cible += 2 * Math.PI ; }
        
        System.out.println( "\nCoordonnées : " + this.getX() + " , " + this.getY() );
        System.out.println( "Cible : " + Environnement.getInstance().getLargeur()/2 + " , " + Environnement.getInstance().getLongueur()/2 );
        System.out.println( "Axe : " + AxeX + " , " + AxeY );
        System.out.println( "Angle vers la cible : "+ Math.toDegrees(angle_cible) );
        System.out.println( "Angle robot : "+ Math.toDegrees(angle) );
        System.out.println( "Différence : " + (angle_cible - angle) );
        this.score = 1 - Math.abs( 
        		(
    					angle_cible
    					-
    					angle
    				)
    				/
    				( Math.PI) );
        
        if( this.score > 0.75)
            this.ptColor = Color.GREEN;
        else if ( this.score > 0.5)
            this.ptColor = Color.BLUE;
        else if ( this.score > 0.25)
            this.ptColor = Color.RED;
        else
        	this.ptColor = Color.BLACK;




        return this.score;
    }

    /**
     * dessine le robot
     */
    public void dessiner(Graphics2D gr)
    {
        this.robot = new Rectangle2D.Double(this.env.getX() + x - 1, this.env.getY() + y - 1, 3, 3);
        Rectangle2D.Double direction = new Rectangle2D.Double(this.env.getX() + x + 3*Math.cos(this.getListe_Sorties()[this.getIndex(Rotation.class.getName())].getValeur()) - 1, this.env.getY() + y - 1 + 3*Math.sin(this.getListe_Sorties()[this.getIndex(Rotation.class.getName())].getValeur()), 1, 1);
        
        gr.setPaint(this.ptColor);
        gr.fill(this.robot);
        gr.fill(direction);
    }

    public void executeActions(IRobot robot)
    {
        int nb = this.listeSorties.length;
        for (int act = 0; act < nb; ++act)
        {
            this.listeSorties[act].action(robot);
        }
    }

    public void updateCapteurs(IRobot robot)
    {
        int nb = this.listeCapteurs.length;
        for (int capt = 0; capt < nb; ++capt)
        {
            this.listeCapteurs[capt].miseAJourCapteur(robot);
        }
    }

    @Override
    public void updateInfos(IRobot robot)
    {
        int nb = this.listeInfos.length;
        for (int inf = 0; inf < nb; ++inf)
        {
            this.listeInfos[inf].miseAJourInfo(robot);
        }
    }

    @Override
    public double[] getValeursNeurones()
    {
        return this.valeursNeurones;
    }

    @Override
    public void setValeursNeurones(double[] valeurs)
    {
        this.valeursNeurones = valeurs;
    }

    /**
     * Permet de savoir si un point est dans le robot
     * @param x
     * @param y
     * @return Vrai quand dedans sinon faux
     */
    public boolean isIn(int x, int y)
    {
        return this.robot.contains(this.env.getX() + x, this.env.getY() + y);
    }
}
